@file:Suppress("DEPRECATION")

package com.script

import com.ScriptField
import com.ScriptMethod
import org.jetbrains.kotlin.cli.common.repl.*
import java.lang.ref.WeakReference
import java.util.concurrent.locks.ReentrantReadWriteLock
import javax.script.CompiledScript
import javax.script.ScriptContext
import javax.script.ScriptEngineFactory
import javax.script.ScriptException
import kotlin.reflect.KType
import kotlin.reflect.KTypeParameter
import kotlin.reflect.full.declaredFunctions
import kotlin.reflect.full.functions
import kotlin.reflect.full.memberProperties
import kotlin.reflect.jvm.isAccessible
import kotlin.script.experimental.api.*
import kotlin.script.experimental.host.ScriptingHostConfiguration
import kotlin.script.experimental.host.withDefaultsFrom
import kotlin.script.experimental.jvm.baseClassLoader
import kotlin.script.experimental.jvm.defaultJvmScriptingHostConfiguration
import kotlin.script.experimental.jvm.jvm
import kotlin.script.experimental.jvmhost.jsr223.KotlinJsr223InvocableScriptEngine
import kotlin.script.experimental.jvmhost.jsr223.getScriptContext
import kotlin.script.experimental.jvmhost.jsr223.jsr223
import kotlin.script.experimental.jvmhost.repl.JvmReplEvaluator
import kotlin.script.experimental.jvmhost.repl.JvmReplEvaluatorState

/**
 * @see kotlin.script.experimental.jvmhost.jsr223.KotlinJsr223ScriptEngineImpl
 */
class KotlinScriptEngine(
    factory: ScriptEngineFactory,
    baseCompilationConfiguration: ScriptCompilationConfiguration,
    baseEvaluationConfiguration: ScriptEvaluationConfiguration,
    val getScriptArgs: (context: ScriptContext) -> ScriptArgsWithTypes?
) : KotlinJsr223JvmScriptEngineBase(factory), KotlinJsr223InvocableScriptEngine {

    @Volatile
    private var lastScriptContext: ScriptContext? = null
    private val asJsr223EvalResult: ((() -> ReplEvalResult) -> Any?) by lazy {
        val evalResult = KotlinJsr223JvmScriptEngineBase::class.declaredFunctions.find { it.name == "asJsr223EvalResult" }
        evalResult?.isAccessible = true
        {
            evalResult?.call(this, it)
        }
    }

    val jsr223HostConfiguration = ScriptingHostConfiguration(defaultJvmScriptingHostConfiguration) {
        val weakThis = WeakReference(this@KotlinScriptEngine)
        jsr223 {
            getScriptContext { weakThis.get()?.let { it.lastScriptContext ?: it.getContext() } }
        }
    }

    val compilationConfiguration by lazy {
        ScriptCompilationConfiguration(baseCompilationConfiguration) {
            hostConfiguration.update { it.withDefaultsFrom(jsr223HostConfiguration) }
            repl {

                getScriptName { id, scriptName ->
                    "script_${scriptName}_${makeDefaultSnippetIdentifier(id)}"
                }

            }
        }
    }

    fun compile(script: String, scriptName: String?): CompiledKotlinScript {
        return compile(script, getContext(), scriptName)
    }

    override fun compile(script: String, context: ScriptContext): CompiledScript {
        return compile(script, context, null)
    }

    fun compile(script: String, context: ScriptContext, scriptName: String? = null): CompiledKotlinScript {
        val codeLine = nextCodeLine(context, script)
        val state = getCurrentState(context)

        codeLine.scriptName = scriptName

        val result = replCompiler.compile(state, codeLine)
        val compiled = when (result) {
            is ReplCompileResult.Error -> throw ScriptException("Error${result.locationString()}: ${result.message}")
            is ReplCompileResult.Incomplete -> throw ScriptException("Error: incomplete code; ${result.message}")
            is ReplCompileResult.CompiledClasses -> result
        }
        return CompiledKotlinScript(this, codeLine, compiled)
//        return super.compile(script, context)
    }

    override fun compileAndEval(script: String, context: ScriptContext): Any? {
        return compileAndEval(script, context, null)
    }

    fun compileAndEval(script: String, context: ScriptContext, scriptName: String?): Any? {
        // 找到一种直接将上下文传递给评估的方法，避免这个hack
        // 目前使用了一个叫做"hack"的临时解决方案来处理上下文传递的问题，将来能找到更直接、更优雅的方式来实现上下文的传递
        lastScriptContext = context
        return try {
            val codeLine = nextCodeLine(context, script)
            codeLine.scriptName = scriptName
            val state = getCurrentState(context)
            asJsr223EvalResult {
                replEvaluator.compileAndEval(state, codeLine, overrideScriptArgs(context), getInvokeWrapper(context))
            }
        } finally {
            lastScriptContext = null
        }
    }


    val evaluationConfiguration by lazy {
        ScriptEvaluationConfiguration(baseEvaluationConfiguration) {
            hostConfiguration.update { it.withDefaultsFrom(jsr223HostConfiguration) }
        }
    }

    override val replCompiler: JvmReplCompiler by lazy {
        JvmReplCompiler(compilationConfiguration)
    }

    private val localEvaluator by lazy {
        GenericReplCompilingEvaluatorBase(replCompiler, JvmReplEvaluator(evaluationConfiguration))
    }

    override val replEvaluator: ReplFullEvaluator get() = localEvaluator

    val state: IReplStageState<*> get() = getCurrentState(getContext())

    override fun createState(lock: ReentrantReadWriteLock): IReplStageState<*> = replEvaluator.createState(lock)

    override fun overrideScriptArgs(context: ScriptContext): ScriptArgsWithTypes? = getScriptArgs(context)

    override val invokeWrapper: InvokeWrapper?
        get() = null

    override val backwardInstancesHistory: Sequence<Any>
        get() = getCurrentState(getContext()).asState(JvmReplEvaluatorState::class.java).history.asReversed()
            .asSequence().map { it.item.second }.filterNotNull()

    override val baseClassLoader: ClassLoader
        get() = evaluationConfiguration[ScriptEvaluationConfiguration.jvm.baseClassLoader]!!


    fun <T : Any> getVarValue(name: String): T? {
        @Suppress("UNCHECKED_CAST")
        return getVar(name).firstOrNull()?.get() as? T
    }

    fun getVar(name: String): List<ScriptField> {
        return this.backwardInstancesHistory.mapNotNull {
            it::class.memberProperties.find { find ->
                find.name == name
            }?.let { v ->
                v.isAccessible = true
                ScriptField(v, it)
            }
        }.toList()
    }

    fun getVar(name: String, type: KType): List<ScriptField> {
        return this.backwardInstancesHistory.mapNotNull {
            it::class.memberProperties.find { find ->
                find.returnType == type && find.name == name
            }?.let { v ->
                v.isAccessible = true
                ScriptField(v, it)
            }
        }.toList()
    }

    fun getFunction(name: String): List<ScriptMethod> {
        return this.backwardInstancesHistory.mapNotNull {
            it::class.functions.find { find ->
                find.name == name
            }?.let { v ->
                ScriptMethod(v, it)
            }
        }.toList()
    }

    fun getFunction(
        name: String,
        vararg types: KTypeParameter
    ): List<ScriptMethod> {
        return this.backwardInstancesHistory.mapNotNull {
            it::class.functions.find { find ->
                find.typeParameters.size == types.size
                        && isValidAll(find.typeParameters, types.toList())
                        && find.name == name
            }?.let { v ->
                ScriptMethod(v, it)
            }
        }.toList()
    }

    fun <T> isValidAll(list: List<T>, list2: List<T>): Boolean {
        list.forEachIndexed { index, t ->
            if (t != list2[index]) {
                return false
            }
        }
        return true
    }

    private fun ReplCompileResult.Error.locationString() =
        if (location == null) ""
        else " at ${location!!.line}:${location!!.column}"


}